package simpledb.parse;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import simpledb.query.Constant;
import simpledb.query.ConstantExpression;
import simpledb.query.Expression;
import simpledb.query.FieldNameExpression;
import simpledb.query.IntConstant;
import simpledb.query.Predicate;
import simpledb.query.StringConstant;
import simpledb.query.Term;
import simpledb.record.Schema;

/**
 * The SimpleDB parser.SImpleDB数据库SQL语句解析器 <br>
 * 数据实现的SQL语句规范
 * 
 * <pre>
 * {Query}      := SELECT {SelectList} FROM {TableList} [ WHERE {Predicate} ]
 * {SelectList} := {Field} [ , {SelectList} ]
 * {TableList}  := TableName [ , {TableList} ]
 * {Predicate}  := {Term} [ AND {Predicate} ]
 * {Term}       := {Expression} = {Expression}
 * {Expression} := {Field} | {Constant}
 * {Field}      := FieldName
 * {Constant}   := String | Integer
 * 
 * {Modify}     := {Insert} | {Delete} | {Update} 
 * {Insert}     := INSERT INTO TableName ( {FieldList} ) VALUES ( {ConstList} )
 * {FieldList}  := {Field} [ , {FieldList} ]
 * {ConstList}  := {Constant} [ , {Constant} ]
 * {Delete}     := DELETE FROM TableName [ WHERE {Predicate} ]
 * {Update}     := UPDATE TableName SET {Field} = {Expression} [ WHERE {Predicate} ]
 * 
 * {Create}     := {CreateTable} | {CreateView} | {CreateIndex}
 * {CreateTable}:= CREATE TABLE TableName ( {FieldDefs} )
 * {FieldDefs}  := {FieldDef} [ , {FieldDefs} ]
 * {FieldDef}   := FieldName {TypeDef}
 * {TypeDef}    := INT | VARCHAR ( Integer )
 * {CreateView} := CREATE VIEW ViewName AS {Query}
 * {CreateIndex}:= CREATE INDEX IndexName ON TableName ( {Field} )
 *
 * </pre>
 * 
 * @author Edward Sciore
 */
public class Parser {
  private Lexer lex;

  public Parser(String s) {
    lex = new Lexer(s);
  }

  // Methods for parsing predicates, terms, expressions, constants, and fields

  public String field() {
    return lex.eatId();
  }

  public Constant constant() {
    if (lex.matchStringConstant())
      return new StringConstant(lex.eatStringConstant());
    else
      return new IntConstant(lex.eatIntConstant());
  }

  public Expression expression() {
    if (lex.matchId())
      return new FieldNameExpression(field());
    else
      return new ConstantExpression(constant());
  }

  public Term term() {
    Expression lhs = expression();
    lex.eatDelim('=');
    Expression rhs = expression();
    return new Term(lhs, rhs);
  }

  public Predicate predicate() {
    Predicate pred = new Predicate(term());
    if (lex.matchKeyword("and")) {
      lex.eatKeyword("and");
      pred.conjoinWith(predicate());
    }
    return pred;
  }

  // Methods for parsing queries

  public QueryData query() {
    lex.eatKeyword("select");
    Collection<String> fields = selectList();
    lex.eatKeyword("from");
    Collection<String> tables = tableList();
    Predicate pred = new Predicate();
    if (lex.matchKeyword("where")) {
      lex.eatKeyword("where");
      pred = predicate();
    }
    return new QueryData(fields, tables, pred);
  }

  private Collection<String> selectList() {
    Collection<String> L = new ArrayList<String>();
    L.add(field());
    while (lex.matchDelim(',')) {
      lex.eatDelim(',');
      L.add(field());
      // L.addAll(selectList());
    }
    return L;
  }

  private Collection<String> tableList() {
    Collection<String> L = new ArrayList<String>();
    L.add(lex.eatId());
    while (lex.matchDelim(',')) {
      lex.eatDelim(',');
      L.add(lex.eatId());
      // L.addAll(tableList());
    }
    return L;
  }

  // Methods for parsing the various update commands

  public Object updateCmd() {
    if (lex.matchKeyword("insert"))
      return insert();
    else if (lex.matchKeyword("delete"))
      return delete();
    else if (lex.matchKeyword("update"))
      return modify();
    else
      return create();
  }

  private Object create() {
    lex.eatKeyword("create");
    if (lex.matchKeyword("table"))
      return createTable();
    else if (lex.matchKeyword("view"))
      return createView();
    else
      return createIndex();
  }

  // Method for parsing delete commands

  public DeleteData delete() {
    lex.eatKeyword("delete");
    lex.eatKeyword("from");
    String tblname = lex.eatId();
    Predicate pred = new Predicate();
    if (lex.matchKeyword("where")) {
      lex.eatKeyword("where");
      pred = predicate();
    }
    return new DeleteData(tblname, pred);
  }

  // Methods for parsing insert commands

  public InsertData insert() {
    lex.eatKeyword("insert");
    lex.eatKeyword("into");
    String tblname = lex.eatId();
    lex.eatDelim('(');
    List<String> flds = fieldList();
    lex.eatDelim(')');
    lex.eatKeyword("values");
    lex.eatDelim('(');
    List<Constant> vals = constList();
    lex.eatDelim(')');
    return new InsertData(tblname, flds, vals);
  }

  private List<String> fieldList() {
    List<String> L = new ArrayList<String>();
    L.add(field());
    while (lex.matchDelim(',')) {
      lex.eatDelim(',');
      L.add(field());
      // L.addAll(fieldList());
    }
    return L;
  }

  private List<Constant> constList() {
    List<Constant> L = new ArrayList<Constant>();
    L.add(constant());
    while (lex.matchDelim(',')) {
      lex.eatDelim(',');
      L.add(constant());
      // L.addAll(constList());
    }
    return L;
  }

  // Method for parsing modify commands

  public ModifyData modify() {
    lex.eatKeyword("update");
    String tblname = lex.eatId();
    lex.eatKeyword("set");
    String fldname = field();
    lex.eatDelim('=');
    Expression newval = expression();
    Predicate pred = new Predicate();
    if (lex.matchKeyword("where")) {
      lex.eatKeyword("where");
      pred = predicate();
    }
    return new ModifyData(tblname, fldname, newval, pred);
  }

  // Method for parsing create table commands

  public CreateTableData createTable() {
    isCreate();
    lex.eatKeyword("table");
    String tblname = lex.eatId();
    lex.eatDelim('(');
    Schema sch = fieldDefs();
    lex.eatDelim(')');
    return new CreateTableData(tblname, sch);
  }

  private Schema fieldDefs() {
    Schema schema = fieldDef();
    while (lex.matchDelim(',')) {
      lex.eatDelim(',');
      Schema schema2 = fieldDef();
      schema.addAll(schema2);
    }
    return schema;
  }

  private Schema fieldDef() {
    String fldname = field();
    return fieldType(fldname);
  }

  private Schema fieldType(String fldname) {
    Schema schema = new Schema();
    if (lex.matchKeyword("int")) {
      lex.eatKeyword("int");
      schema.addIntField(fldname);
    } else {
      lex.eatKeyword("varchar");
      lex.eatDelim('(');
      int strLen = lex.eatIntConstant();
      lex.eatDelim(')');
      schema.addStringField(fldname, strLen);
    }
    return schema;
  }

  // Method for parsing create view commands

  public CreateViewData createView() {
    isCreate();
    lex.eatKeyword("view");
    String viewname = lex.eatId();
    lex.eatKeyword("as");
    QueryData qd = query();
    return new CreateViewData(viewname, qd);
  }

  // Method for parsing create index commands

  public CreateIndexData createIndex() {
    isCreate();
    lex.eatKeyword("index");
    String idxname = lex.eatId();
    lex.eatKeyword("on");
    String tblname = lex.eatId();
    lex.eatDelim('(');
    String fldname = field();
    lex.eatDelim(')');
    return new CreateIndexData(idxname, tblname, fldname);
  }

  private void isCreate() {
    if (lex.matchKeyword("create")) {
      lex.eatKeyword("create");
    }
  }
}
